home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / mxlibs / smix118 / smix.pas < prev    next >
Pascal/Delphi Source File  |  1995-05-27  |  31KB  |  863 lines

  1. {       SMIX is Copyright 1995 by Ethan Brodsky.  All rights reserved.       }
  2.  
  3. unit SMix; {Version 1.18}
  4.  {$X+} {$G+} {$R-}
  5.   interface
  6.     const
  7.       BlockLength   = 512;      {Size of digitized sound block               }
  8.       LoadChunkSize = 2048;     {Chunk size used for loading sounds from disk}
  9.       Voices        = 8;        {Number of available voices                  }
  10.     type
  11.       PSound = ^TSound;
  12.       TSound =
  13.         record
  14.           XMSHandle: word;
  15.           StartOfs:  LongInt;
  16.           SoundSize: LongInt;
  17.         end;
  18.     function InitSB(BaseIO: word; IRQ: byte; DMA, DMA16: byte): boolean;
  19.       {Initializes control parameters, resets DSP, and installs int. handler }
  20.       { Parameters: (Can be found using GetSettings procedure in Detect)     }
  21.       {  BaseIO:   Sound card base IO address                                }
  22.       {  IRQ:      Sound card IRQ setting                                    }
  23.       {  DMA:      Sound card 8-bit DMA channel                              }
  24.       {  DMA16:    Sound card 16-bit DMA channel (0 if not supported)        }
  25.       { Returns:                                                             }
  26.       {  TRUE:     Sound card successfully initialized (Maybe)               }
  27.       {  FALSE:    Sound card could not be initialized                       }
  28.     procedure ShutdownSB;
  29.       {Removes interrupt handler and resets DSP                              }
  30.  
  31.     procedure InitMixing;
  32.       {Allocates internal buffers and starts digitized sound output          }
  33.     procedure ShutdownMixing;
  34.       {Deallocates internal buffers and stops digitized sound output         }
  35.  
  36.     function  InitXMS: boolean;
  37.       {Attempts to intialize extended memory                                 }
  38.       { Returns:                                                             }
  39.       {  TRUE:     Extended memory successfully initialized                  }
  40.       {  FALSE:    Extended memory could not be initialized                  }
  41.     function  GetFreeXMS: word;
  42.       {Returns amount of free XMS memory (In kilobytes)                      }
  43.  
  44.     procedure InitSharing;
  45.       {Allocates an EMB that all sounds are stored in.  This preserves EMB   }
  46.       {handles, which are a scarce resource.  Call this on initialization and}
  47.       {all sounds will automatically be stored in one EMB.  Call LoadSound as}
  48.       {usual to allocate a sound, but FreeSound only deallocates the sound   }
  49.       {data structure.  Call ShutdownSharing before program termination to   }
  50.       {free allocated extended memory.                                       }
  51.     procedure ShutdownSharing;
  52.       {Shuts down EMB sharing and frees all allocated extended memory        }
  53.  
  54.     procedure LoadSound(var Sound: PSound; FileName: string);
  55.       {Allocates an extended memory block and loads a sound from a file      }
  56.       { Parameters:                                                          }
  57.       {  Sound:    Unallocated pointer to sound data structure               }
  58.       {  FileName: File to load sound from (Raw signed digitized sound data) }
  59.     procedure FreeSound(var Sound: PSound);
  60.       {Deallocates extended memory and destroys sound data structure         }
  61.       { Parameters:                                                          }
  62.       {  Sound:    Unallocated pointer to sound data structure               }
  63.  
  64.     procedure StartSound(Sound: PSound; Index: byte; Loop: boolean);
  65.       {Starts playing a sound                                                }
  66.       { Parameters:                                                          }
  67.       {  Sound:    Pointer to sound data structure                           }
  68.       {  Index:    A number to keep track of the sound with (Used to stop it)}
  69.       {  Loop:     Indicates whether the sound should be continuously looped }
  70.     procedure StopSound(Index: byte);
  71.       {Stops playing sound                                                   }
  72.       { Parameters:                                                          }
  73.       {  Index:    Index of sound to stop (All with given index are stopped) }
  74.     function SoundPlaying(Index: byte): boolean;
  75.       {Checks if a sound is still playing                                    }
  76.       { Parameters:                                                          }
  77.       {  Index:    Index used when the sound was started                     }
  78.       { Returns:                                                             }
  79.       {  TRUE      At least oen sound with the specified index is playing    }
  80.       {  FALSE     No sounds with the specified index are playing            }
  81.  
  82.     var
  83.       IntCount   : LongInt;  {Number of sound interrupts that have occured   }
  84.       DSPVersion : real;     {Contains the version of the installed DSP chip }
  85.       AutoInit   : boolean;  {Tells Auto-initialized DMA transfers are in use}
  86.       SixteenBit : boolean;  {Tells whether 16-bit sound output is occuring  }
  87.       VoiceCount : byte;     {Number of voices currently in use              }
  88.  
  89.   implementation
  90.     uses
  91.       CRT,
  92.       DOS,
  93.       XMS;
  94.     const
  95.       BufferLength = BlockLength * 2;
  96.     var
  97.       ResetPort        : word;
  98.       ReadPort         : word;
  99.       WritePort        : word;
  100.       PollPort         : word;
  101.       AckPort          : word;
  102.  
  103.       PICRotatePort    : word;
  104.       PICMaskPort      : word;
  105.  
  106.       DMAMaskPort      : word;
  107.       DMAClrPtrPort    : word;
  108.       DMAModePort      : word;
  109.       DMABaseAddrPort  : word;
  110.       DMACountPort     : word;
  111.       DMAPagePort      : word;
  112.  
  113.       IRQStartMask     : byte;
  114.       IRQStopMask      : byte;
  115.       IRQIntVector     : byte;
  116.  
  117.       DMAStartMask     : byte;
  118.       DMAStopMask      : byte;
  119.       DMAMode          : byte;
  120.       DMALength        : word;
  121.  
  122.       OldIntVector     : pointer;
  123.       OldExitProc      : pointer;
  124.  
  125.       HandlerInstalled : boolean;
  126.  
  127.     procedure WriteDSP(Value: byte);
  128.       begin
  129.         repeat until (Port[WritePort] and $80) = 0;
  130.         Port[WritePort] := Value;
  131.       end;
  132.  
  133.     function ReadDSP: byte;
  134.       begin
  135.         repeat until (Port[PollPort] and $80) <> 0;
  136.         ReadDSP := Port[ReadPort];
  137.       end;
  138.  
  139.     function ResetDSP: boolean;
  140.       var
  141.         i: byte;
  142.       begin
  143.         Port[ResetPort] := 1;
  144.         Delay(1);                              {One millisecond}
  145.         Port[ResetPort] := 0;
  146.         i := 100;
  147.         while (ReadDSP <> $AA) and (i > 0) do Dec(i);
  148.         if i > 0
  149.           then ResetDSP := true
  150.           else ResetDSP := false;
  151.       end;
  152.  
  153.     procedure InstallHandler; forward;
  154.     procedure UninstallHandler; forward;
  155.  
  156.     procedure MixExitProc; far; forward;
  157.  
  158.     function InitSB(BaseIO: word; IRQ: byte; DMA, DMA16: byte): boolean;
  159.       begin
  160.        {Sound card IO ports}
  161.         ResetPort  := BaseIO + $6;
  162.         ReadPort   := BaseIO + $A;
  163.         WritePort  := BaseIO + $C;
  164.         PollPort   := BaseIO + $E;
  165.  
  166.        {Reset DSP, get version, and pick output mode}
  167.         if not(ResetDSP)
  168.           then
  169.             begin
  170.               InitSB := false;
  171.               Exit;
  172.             end;
  173.         WriteDSP($E1);  {Get DSP version number}
  174.         DSPVersion := ReadDSP;  DSPVersion := DSPVersion + ReadDSP/100;
  175.         AutoInit := DSPVersion > 2.0;
  176.         SixteenBit := (DSPVersion > 4.0) and (DMA16 <> $FF);
  177.  
  178.        {Compute interrupt ports and parameters}
  179.         if IRQ <= 7
  180.           then
  181.             begin
  182.               IRQIntVector  := $08+IRQ;
  183.               PICMaskPort   := $21;
  184.             end
  185.           else
  186.             begin
  187.               IRQIntVector  := $70+IRQ-8;
  188.               PICMaskPort   := $A1;
  189.             end;
  190.         IRQStopMask  := 1 shl (IRQ mod 8);
  191.         IRQStartMask := not(IRQStopMask);
  192.  
  193.        {Compute DMA ports and parameters}
  194.         if SixteenBit
  195.           then {Sixteen bit}
  196.             begin
  197.               DMAMaskPort     := $D4;
  198.               DMAClrPtrPort   := $D8;
  199.               DMAModePort     := $D6;
  200.               DMABaseAddrPort := $C0 + 4*(DMA16-4);
  201.               DMACountPort    := $C2 + 4*(DMA16-4);
  202.               case DMA16
  203.                 of
  204.                   5:  DMAPagePort := $8B;
  205.                   6:  DMAPagePort := $89;
  206.                   7:  DMAPagePort := $8A;
  207.                 end;
  208.               DMAStopMask  := DMA16-4 + $04;   {000001xx}
  209.               DMAStartMask := DMA16-4 + $00;   {000000xx}
  210.               DMAMode      := DMA16-4 + $58;   {010110xx}
  211.               AckPort := BaseIO + $F;
  212.             end
  213.           else {Eight bit}
  214.             begin
  215.               DMAMaskPort     := $0A;
  216.               DMAClrPtrPort   := $0C;
  217.               DMAModePort     := $0B;
  218.               DMABaseAddrPort := $00 + 2*DMA;
  219.               DMACountPort    := $01 + 2*DMA;
  220.               case DMA
  221.                 of
  222.                   0:  DMAPagePort := $87;
  223.                   1:  DMAPagePort := $83;
  224.                   2:  DMAPagePort := $81;
  225.                   3:  DMAPagePort := $82;
  226.                 end;
  227.               DMAStopMask  := DMA + $04;       {000001xx}
  228.               DMAStartMask := DMA + $00;       {000000xx}
  229.               if AutoInit
  230.                 then DMAMode := DMA + $58      {010110xx}
  231.                 else DMAMode := DMA + $48;     {010010xx}
  232.               AckPort := BaseIO + $E;
  233.             end;
  234.           if AutoInit
  235.             then DMALength := BufferLength
  236.             else DMALength := BlockLength;
  237.           InstallHandler;
  238.  
  239.           OldExitProc := ExitProc;
  240.           ExitProc    := @MixExitProc;
  241.           InitSB := true;
  242.       end;
  243.  
  244.     procedure ShutdownSB;
  245.       begin
  246.         if HandlerInstalled
  247.           then UninstallHandler;
  248.         ResetDSP;
  249.       end;
  250.  
  251.     function InitXMS: boolean;
  252.       begin
  253.         InitXMS := true;
  254.         if not(XMSInstalled)
  255.           then InitXMS := false
  256.           else XMSInit;
  257.       end;
  258.     function GetFreeXMS: word;
  259.       begin
  260.         GetFreeXMS := XMSGetFreeMem;
  261.       end;
  262.  
  263.    {Voice control}
  264.     type
  265.       PVoice = ^TVoice;
  266.       TVoice =
  267.         record
  268.           Sound:     PSound;
  269.           Index:     byte;
  270.           CurPos:    LongInt;
  271.           Loop:      boolean;
  272.         end;
  273.     var
  274.       VoiceInUse: array[0..Voices-1] of boolean;
  275.       Voice:      array[0..Voices-1] of TVoice;
  276.       CurBlock:   byte;
  277.    {Sound buffer}
  278.     var
  279.       SoundBlock: array[1..BlockLength+1] of ShortInt;
  280.         {The length of XMS copies under HIMEM.SYS must be a mutiple  }
  281.         {of two.  If the sound data ends in mid-block, it may not be }
  282.         {possible to round up without corrupting memory.  Therefore, }
  283.         {the copy buffer has been extended by one byte to eliminate  }
  284.         {this problem.                                               }
  285.  
  286.    {Mixing buffers}
  287.     type
  288.       PMixingBlock = ^TMixingBlock;
  289.       TMixingBlock = array[1..BlockLength] of integer;
  290.     var
  291.       MixingBlock  : TMixingBlock;
  292.  
  293.    {Output buffers}
  294.     type {8-bit}
  295.       POut8Block   = ^TOut8Block;
  296.       TOut8Block   = array[1..BlockLength] of byte;
  297.       POut8Buffer  = ^TOut8Buffer;
  298.       TOut8Buffer  = array[1..2] of TOut8Block;
  299.     type {16-bit}
  300.       POut16Block  = ^TOut16Block;
  301.       TOut16Block  = array[1..BlockLength] of integer;
  302.       POut16Buffer = ^TOut16Buffer;
  303.       TOut16Buffer = array[1..2] of TOut16Block;
  304.     var
  305.       OutMemArea  : pointer;
  306.       Out8Buffer  : POut8Buffer;
  307.       Out16Buffer : POut16Buffer;
  308.     var
  309.       BlockPtr    : array[1..2] of pointer;
  310.       CurBlockPtr : pointer;
  311.     var
  312.      {For auto-initialized transfers (Whole buffer)}
  313.       BufferAddr : LongInt;
  314.       BufferPage : byte;
  315.       BufferOfs  : word;
  316.      {For single-cycle transfers (One block at a time)}
  317.       BlockAddr  : array[1..2] of LongInt;
  318.       BlockPage  : array[1..2] of byte;
  319.       BlockOfs   : array[1..2] of word;
  320.  
  321.     {Clipping for 8-bit output}
  322.      var
  323.        Clip8 : array[-128*Voices..128*Voices] of byte;
  324.  
  325.     procedure StartDAC;
  326.       begin
  327.         Port[DMAMaskPort]     := DMAStopMask;
  328.         Port[DMAClrPtrPort]   := $00;
  329.         Port[DMAModePort]     := DMAMode;
  330.         Port[DMABaseAddrPort] := Lo(BufferOfs);
  331.         Port[DMABaseAddrPort] := Hi(BufferOfs);
  332.         Port[DMACountPort]    := Lo(DMALength-1);
  333.         Port[DMACountPort]    := Hi(DMALength-1);
  334.         Port[DMAPagePort]     := BufferPage;
  335.         Port[DMAMaskPort]     := DMAStartMask;
  336.  
  337.         if SixteenBit
  338.           then {Sixteen bit: SB16 and up (DSP 4.xx)}
  339.             begin
  340.               WriteDSP($41);        {Set digitized sound output sampling rate}
  341.               WriteDSP(Hi(22050));  {  Hi byte - $56     22050 HZ            }
  342.               WriteDSP(Lo(22050));  {  Lo byte - $22                         }
  343.               WriteDSP($B6);        {16-bit DMA command: D/A, Auto-Init, FIFO}
  344.               WriteDSP($10);        {16-bit DMA mode:    Signed Mono         }
  345.               WriteDSP(Lo(BlockLength - 1));
  346.               WriteDSP(Hi(BlockLength - 1));
  347.             end
  348.           else {Eight bit}
  349.             begin
  350.               WriteDSP($D1);        {Turn on speaker                         }
  351.               WriteDSP($40);        {Set digitized sound time constant       }
  352.               WriteDSP(211);        { = 256 - (1000000 / SamplingRate)       }
  353.               if AutoInit
  354.                 then {Eight bit auto-initialized: SBPro and up (DSP 2.00+)}
  355.                   begin
  356.                     WriteDSP($48);  {Set DSP block transfer size             }
  357.                     WriteDSP(Lo(BlockLength - 1));
  358.                     WriteDSP(Hi(BlockLength - 1));
  359.                     WriteDSP($1C);  {8-bit auto-init DMA mono sound output   }
  360.                   end
  361.                 else {Eight bit single-cycle: Sound Blaster (DSP 1.xx+)}
  362.                   begin
  363.                     WriteDSP($14);  {8-bit single-cycle DMA sound output     }
  364.                     WriteDSP(Lo(BlockLength - 1));
  365.                     WriteDSP(Hi(BlockLength - 1));
  366.                   end;
  367.             end;
  368.       end;
  369.  
  370.     procedure StopDAC;
  371.       begin
  372.         if SixteenBit
  373.           then {Sixteen bit}
  374.             begin
  375.               WriteDSP($D5);        {Pause 16-bit DMA sound I/O              }
  376.             end
  377.           else {Eight bit}
  378.             begin
  379.               WriteDSP($D0);        {Pause 8-bit DMA mode sound I/O          }
  380.               WriteDSP($D3);        {Turn off speaker                        }
  381.             end;
  382.         Port[DMAMaskPort] := DMAStopMask;
  383.       end;
  384.  
  385.    {Setup for storing all sounds in one extended memory block (Saves handles)}
  386.     var
  387.       SharedEMB    : boolean;
  388.       SharedHandle : word;
  389.       SharedSize   : LongInt;
  390.     procedure InitSharing;
  391.       begin
  392.         SharedEMB  := true;
  393.         SharedSize := 0;
  394.         XMSAllocate(SharedHandle, SharedSize);
  395.       end;
  396.     procedure ShutdownSharing;
  397.       begin
  398.         if SharedEMB then XMSFree(SharedHandle);
  399.         SharedEMB := false;
  400.       end;
  401.  
  402.    {Loading and freeing sounds}
  403.     var
  404.       MoveParams: TMoveParams; {The XMS driver doesn't like this on the stack}
  405.     procedure LoadSound(var Sound: PSound; FileName: string);
  406.       var
  407.         f: file;
  408.         Size: LongInt;
  409.         InBuffer: array[1..LoadChunkSize] of byte;
  410.       begin
  411.         Assign(f, FileName);  Reset(f, 1);
  412.  
  413.         Size := FileSize(f);
  414.         New(Sound);
  415.         Sound^.SoundSize := Size;
  416.  
  417.         if not(SharedEMB)
  418.           then
  419.             begin
  420.               Sound^.StartOfs := 0;
  421.               XMSAllocate(Sound^.XMSHandle, (Size + 1023) div 1024);
  422.             end
  423.           else
  424.             begin
  425.               Sound^.StartOfs := SharedSize;
  426.               Sound^.XMSHandle := SharedHandle;
  427.               SharedSize := SharedSize + Size;
  428.               XMSReallocate(SharedHandle, (SharedSize + 1023) div 1024);
  429.             end;
  430.         MoveParams.SourceHandle := 0;
  431.         MoveParams.SourceOffset := LongInt(Addr(InBuffer));
  432.         MoveParams.DestHandle   := Sound^.XMSHandle;
  433.         MoveParams.DestOffset   := Sound^.StartOfs;
  434.  
  435.  
  436.         repeat
  437.           BlockRead(f, InBuffer, LoadChunkSize, word(MoveParams.Length));
  438.           MoveParams.Length := ((MoveParams.Length+1) div 2) * 2;
  439.             {XMS copy lengths must be a multiple of two}
  440.           XMSMove(@MoveParams);
  441.           MoveParams.DestOffset := MoveParams.DestOffset + MoveParams.Length;
  442.         until (MoveParams.Length = 0);
  443.  
  444.         Close(f);
  445.       end;
  446.  
  447.     procedure FreeSound(var Sound: PSound);
  448.       begin
  449.         if not(SharedEMB) then XMSFree(Sound^.XMSHandle);
  450.         Dispose(Sound); Sound := nil;
  451.       end;
  452.  
  453.    {Voice maintainance}
  454.     procedure DeallocateVoice(VoiceNum: byte);
  455.       begin
  456.         VoiceInUse[VoiceNum] := false;
  457.         with Voice[VoiceNum] do
  458.           begin
  459.             Sound    := nil;
  460.             Index    := 0;
  461.             CurPos   := 0;
  462.             Loop     := false;
  463.           end;
  464.       end;
  465.  
  466.     procedure StartSound(Sound: PSound; Index: byte; Loop: boolean);
  467.       var
  468.         i, Slot: byte;
  469.       begin
  470.         Slot := $FF; i := 0;
  471.         repeat
  472.           if not(VoiceInUse[i])
  473.             then Slot := i;
  474.           Inc(i);
  475.         until ((Slot <> $FF) or (i=Voices));
  476.         if Slot <> $FF
  477.           then
  478.             begin
  479.               Inc(VoiceCount);
  480.               Voice[Slot].Sound    := Sound;
  481.               Voice[Slot].Index    := Index;
  482.               Voice[Slot].CurPos   := 0;
  483.               Voice[Slot].Loop     := Loop;
  484.  
  485.               VoiceInUse[Slot] := true;
  486.             end;
  487.       end;
  488.  
  489.     procedure StopSound(Index: byte);
  490.       var
  491.         i: byte;
  492.       begin
  493.         for i := 0 to Voices-1 do
  494.           if Voice[i].Index = Index
  495.             then
  496.               begin
  497.                 DeallocateVoice(i);
  498.                 Dec(VoiceCount);
  499.               end;
  500.       end;
  501.  
  502.     function SoundPlaying(Index: byte): boolean;
  503.       var
  504.         i: byte;
  505.       begin
  506.         SoundPlaying := False;
  507.  
  508.         for i := 0 to Voices-1 do
  509.           if Voice[i].Index = Index
  510.             then SoundPlaying := True;
  511.       end;
  512.  
  513.     procedure UpdateVoices;
  514.       var
  515.         VoiceNum: byte;
  516.       begin
  517.         for VoiceNum := 0 to Voices-1 do
  518.           begin
  519.             if VoiceInUse[VoiceNum]
  520.               then
  521.                 if Voice[VoiceNum].CurPos >= Voice[VoiceNum].Sound^.SoundSize
  522.                   then
  523.                     begin
  524.                       DeallocateVoice(VoiceNum);
  525.                       Dec(VoiceCount);
  526.                     end;
  527.           end;
  528.       end;
  529.  
  530.  
  531.    {Utility functions}
  532.     procedure SetCurBlock(BlockNum: byte);
  533.       begin
  534.         CurBlock := BlockNum;
  535.         CurBlockPtr := pointer(BlockPtr[BlockNum]);
  536.       end;
  537.  
  538.     procedure ToggleBlock;
  539.       begin
  540.         if CurBlock = 1
  541.           then SetCurBlock(2)
  542.           else SetCurBlock(1);
  543.       end;
  544.  
  545.     procedure SilenceBlock;
  546.       begin
  547.         FillChar(MixingBlock, BlockLength*2, 0);  {FillChar uses REP STOSW}
  548.       end;
  549.  
  550.     function GetLinearAddr(Ptr: pointer): LongInt;
  551.       begin
  552.         GetLinearAddr := LongInt(Seg(Ptr^))*16 + LongInt(Ofs(Ptr^));
  553.       end;
  554.  
  555.     function NormalizePtr(p: pointer): pointer;
  556.       var
  557.         LinearAddr: LongInt;
  558.       begin
  559.         LinearAddr := GetLinearAddr(p);
  560.         NormalizePtr := Ptr(LinearAddr div 16, LinearAddr mod 16);
  561.       end;
  562.  
  563.  
  564.     procedure InitClip8;
  565.       var
  566.         i, Value: integer;
  567.       begin
  568.         for i := -128*Voices to 128*Voices do
  569.           begin
  570.             Value := i;
  571.             if (Value < -128) then Value := -128;
  572.             if (Value > +127) then Value := +127;
  573.  
  574.             Clip8[i] := Value + 128;
  575.           end;
  576.       end;
  577.  
  578.     procedure InitMixing;
  579.       var
  580.         i: integer;
  581.       begin
  582.         for i := 0 to Voices-1 do DeallocateVoice(i);
  583.         VoiceCount := 0;
  584.  
  585.         if SixteenBit
  586.           then
  587.             begin
  588.              {Find a block of memory that does not cross a page boundary}
  589.               GetMem(OutMemArea, 4*BufferLength);
  590.               if ((GetLinearAddr(OutMemArea) div 2) mod 65536)+BufferLength < 65536
  591.                 then Out16Buffer := OutMemArea
  592.                 else Out16Buffer := NormalizePtr(Ptr(Seg(OutMemArea^), Ofs(OutMemArea^)+2*BufferLength));
  593.               for i := 1 to 2 do
  594.                 BlockPtr[i] := NormalizePtr(Addr(Out16Buffer^[i]));
  595.              {DMA parameters}
  596.               BufferAddr := GetLinearAddr(pointer(Out16Buffer));
  597.               BufferPage := BufferAddr div 65536;
  598.               BufferOfs  := (BufferAddr div 2) mod 65536;
  599.               for i := 1 to 2 do
  600.                 BlockAddr[i] := GetLinearAddr(pointer(BlockPtr[i]));
  601.               for i := 1 to 2 do
  602.                 BlockPage[i] := BlockAddr[i] div 65536;
  603.               for i := 1 to 2 do
  604.                 BlockOfs[i]  := (BlockAddr[i] div 2) mod 65536;
  605.               FillChar(Out16Buffer^, BufferLength*2, $00);   {Signed   16-bit}
  606.             end
  607.           else
  608.             begin
  609.              {Find a block of memory that does not cross a page boundary}
  610.               GetMem(OutMemArea, 2*BufferLength);
  611.               if (GetLinearAddr(OutMemArea) mod 65536)+BufferLength < 65536
  612.                 then Out8Buffer := OutMemArea
  613.                 else Out8Buffer := NormalizePtr(Ptr(Seg(OutMemArea^), Ofs(OutMemArea^)+BufferLength));
  614.               for i := 1 to 2 do
  615.                 BlockPtr[i] := NormalizePtr(Addr(Out8Buffer^[i]));
  616.              {DMA parameters}
  617.               BufferAddr := GetLinearAddr(pointer(Out8Buffer));
  618.               BufferPage := BufferAddr div 65536;
  619.               BufferOfs  := BufferAddr mod 65536;
  620.               for i := 1 to 2 do
  621.                 BlockAddr[i] := GetLinearAddr(pointer(BlockPtr[i]));
  622.               for i := 1 to 2 do
  623.                 BlockPage[i] := BlockAddr[i] div 65536;
  624.               for i := 1 to 2 do
  625.                 BlockOfs[i]  := BlockAddr[i] mod 65536;
  626.               FillChar(Out8Buffer^, BufferLength, $80);      {Unsigned  8-bit}
  627.  
  628.               InitClip8;
  629.             end;
  630.  
  631.         FillChar(MixingBlock, BlockLength*2, $00);
  632.  
  633.         SetCurBlock(1);
  634.         IntCount := 0;
  635.         StartDAC;
  636.       end;
  637.  
  638.     procedure ShutdownMixing;
  639.       begin
  640.         StopDAC;
  641.  
  642.         if SixteenBit
  643.           then FreeMem(OutMemArea, 4*BufferLength)
  644.           else FreeMem(OutMemArea, 2*BufferLength);
  645.       end;
  646.  
  647.  
  648.  
  649.     var {The XMS driver doesn't like parameter blocks in the stack}
  650.       IntMoveParams: TMoveParams;  {In case LoadSound is interrupted}
  651.     procedure CopySound(Sound: PSound; var CurPos: LongInt; CopyLength: word; Loop: boolean);
  652.       var
  653.         SoundSize: LongInt;
  654.         DestPtr: pointer;
  655.       begin
  656.         SoundSize := Sound^.SoundSize;
  657.         DestPtr := pointer(@SoundBlock);
  658.         IntMoveParams.SourceHandle := Sound^.XMSHandle;
  659.         IntMoveParams.DestHandle   := 0;
  660.         while CopyLength > 0 do
  661.           begin
  662.            {Compute max transfer size}
  663.             if CopyLength < SoundSize-CurPos
  664.               then IntMoveParams.Length := CopyLength
  665.               else IntMoveParams.Length := SoundSize-CurPos;
  666.  
  667.            {Compute starting dest. offset and update offset for next block}
  668.             IntMoveParams.SourceOffset := Sound^.StartOfs + CurPos;
  669.             CurPos := CurPos + IntMoveParams.Length;
  670.             if Loop then CurPos := CurPos mod SoundSize;
  671.  
  672.            {Compute starting source offset and update offset for next block}
  673.             IntMoveParams.DestOffset := LongInt(DestPtr);
  674.             DestPtr := NormalizePtr(Ptr(Seg(DestPtr^), Ofs(DestPtr^)+IntMoveParams.Length));
  675.  
  676.            {Update remaining count for next iteration}
  677.             CopyLength := CopyLength - IntMoveParams.Length;
  678.  
  679.            {Move block}
  680.             IntMoveParams.Length := ((IntMoveParams.Length+1) div 2) * 2;
  681.               {XMS copy lengths must be a multiple of two}
  682.             XMSMove(@IntMoveParams);  {Luckily, the XMS driver is re-entrant}
  683.           end;
  684.       end;
  685.  
  686.     procedure MixVoice(VoiceNum: byte);
  687.       var
  688.         MixLength: word;
  689.       begin
  690.         with Voice[VoiceNum] do
  691.           if Loop
  692.             then
  693.               MixLength := BlockLength
  694.             else
  695.               if BlockLength < Sound^.SoundSize-CurPos
  696.                 then MixLength := BlockLength
  697.                 else MixLength := Sound^.SoundSize-CurPos;
  698.         CopySound(Voice[VoiceNum].Sound, Voice[VoiceNum].CurPos, MixLength, Voice[VoiceNum].Loop);
  699.         asm
  700.           lea  si, SoundBlock         {DS:SI -> Sound data (Source)          }
  701.           mov  ax, ds                 {ES:DI -> Mixing block (Destination)   }
  702.           mov  es, ax
  703.           lea  di, MixingBlock
  704.           mov  cx, MixLength          {CX = Number of samples to copy        }
  705.           cld                         {Left-to-right string operation        }
  706.  
  707.          @MixSample:
  708.           mov  al, [si]               {Load a sample from the sound block    }
  709.           inc  si                     { increment pointer                    }
  710.           cbw                         {Convert it to a 16-bit signed sample  }
  711.           add  es:[di], ax            {Add it into the mixing buffer         }
  712.           add  di, 2                  {Next word in mixing buffer            }
  713.           dec  cx                     {Loop for next sample                  }
  714.           jnz  @MixSample
  715.         end;
  716.       end;
  717.  
  718.     procedure MixVoices;
  719.       var
  720.         i: word;
  721.       begin
  722.         SilenceBlock;
  723.         for i := 0 to Voices-1 do
  724.           if VoiceInUse[i]
  725.             then
  726.               MixVoice(i);
  727.       end;
  728.  
  729.     procedure CopyData16; assembler;
  730.       asm
  731.         push  ds
  732.  
  733.         lea   si, MixingBlock         {DS:SI -> 16-bit input block           }
  734.         les   di, [CurBlockPtr]       {ES:DI -> 16-bit output block          }
  735.         mov   cx, BlockLength         {CX = Number of samples to copy        }
  736.         cld                           {Left-to-right string operation        }
  737.  
  738.        @CopySample:
  739.         mov   ax, [si]                {Load a sample from the mixing block   }
  740.         add   si, 2                   {Increment source pointer              }
  741.         sal   ax, 5                   {Shift sample left to fill 16-bit range}
  742.         mov   es:[di], ax             {Store sample in output block          }
  743.         add   di, 2                   {Increment destination pointer         }
  744.         dec   cx                      {Process the next sample               }
  745.         jnz   @CopySample
  746.  
  747.         pop   ds
  748.       end;
  749.  
  750.     procedure CopyData8; assembler;
  751.       asm
  752.         push  ds
  753.         push  bp
  754.  
  755.         lea   si, Clip8               {DS:SI -> 8-bit clipping buffer        }
  756.         add   si, 128*Voices          {DS:SI -> Center of clipping buffer    }
  757.  
  758.         lea   bp, MixingBlock         {DS:BP -> 16-bit input block           }
  759.         les   di, [CurBlockPtr]       {ES:DI -> 8-bit output block           }
  760.         mov   cx, BlockLength         {CX = Number of samples to copy        }
  761.         cld                           {Left-to-right string operation        }
  762.  
  763.        @CopySample:
  764.         mov   bx, ds:[bp]             {SI = Sample from mixing block         }
  765.         add   bp, 2                   {Increment source pointer (BP)         }
  766.         mov   al, [si+bx]             {AL = Clipped sample to output         }
  767.         mov   es:[di], al             {Store sample in output block          }
  768.         inc   di                      {Increment destination pointer (DI)    }
  769.         dec   cx                      {Process the next sample               }
  770.         jnz   @CopySample
  771.  
  772.         pop   bp
  773.         pop   ds
  774.       end;
  775.  
  776.     procedure CopyData;
  777.       begin
  778.         if SixteenBit
  779.           then CopyData16
  780.           else CopyData8;
  781.       end;
  782.  
  783.     procedure StartBlock_SC; {Starts a single-cycle DMA transfer}
  784.       begin
  785.         Port[DMAMaskPort]     := DMAStopMask;
  786.         Port[DMAClrPtrPort]   := $00;
  787.         Port[DMAModePort]     := DMAMode;
  788.         Port[DMABaseAddrPort] := Lo(BlockOfs[CurBlock]);
  789.         Port[DMABaseAddrPort] := Hi(BlockOfs[CurBlock]);
  790.         Port[DMACountPort]    := Lo(DMALength-1);
  791.         Port[DMACountPort]    := Hi(DMALength-1);
  792.         Port[DMAPagePort]     := BlockPage[CurBlock];
  793.         Port[DMAMaskPort]     := DMAStartMask;
  794.         WriteDSP($14);                {8-bit single-cycle DMA sound output   }
  795.         WriteDSP(Lo(BlockLength - 1));
  796.         WriteDSP(Hi(BlockLength - 1));
  797.       end;
  798.  
  799.     procedure IntHandler; interrupt;
  800.       var
  801.         Temp: byte;
  802.       begin
  803.         Inc(IntCount);
  804.  
  805.         if not(AutoInit) {Start next block first if not using auto-init DMA}
  806.           then
  807.             begin
  808.               StartBlock_SC;
  809.               CopyData;
  810.               ToggleBlock;
  811.             end;
  812.  
  813.         UpdateVoices;
  814.         MixVoices;
  815.  
  816.         if (AutoInit)
  817.           then
  818.             begin
  819.               CopyData;
  820.               ToggleBlock;
  821.             end;
  822.  
  823.         Temp := Port[AckPort];
  824.         Port[$A0] := $20;
  825.         Port[$20] := $20;
  826.       end;
  827.  
  828.     procedure EnableInterrupts;  InLine($FB); {STI}
  829.     procedure DisableInterrupts; InLine($FA); {CLI}
  830.  
  831.     procedure InstallHandler;
  832.       begin
  833.         DisableInterrupts;
  834.         Port[PICMaskPort] := Port[PICMaskPort] or IRQStopMask;
  835.         GetIntVec(IRQIntVector, OldIntVector);
  836.         SetIntVec(IRQIntVector, @IntHandler);
  837.         Port[PICMaskPort] := Port[PICMaskPort] and IRQStartMask;
  838.         EnableInterrupts;
  839.         HandlerInstalled := true;
  840.       end;
  841.  
  842.     procedure UninstallHandler;
  843.       begin
  844.         DisableInterrupts;
  845.         Port[PICMaskPort] := Port[PICMaskPort] or IRQStopMask;
  846.         SetIntVec(IRQIntVector, OldIntVector);
  847.         EnableInterrupts;
  848.         HandlerInstalled := false;
  849.       end;
  850.  
  851.     procedure MixExitProc;       {Called automatically on program termination}
  852.       begin
  853.         ExitProc := OldExitProc;
  854.  
  855.         StopDAC;
  856.         ShutdownSB;
  857.       end;
  858.  
  859.   begin
  860.     HandlerInstalled := false;
  861.     SharedEMB        := false;
  862.   end.
  863.